/**
 *  @file SocketFactory.cpp
 */
#include "SocketFactory.h"
#include "../common/CppMemPool.h"
#include "../text/CFString.h"
#include "../exceptions/RuntimeException.h"

using namespace cppflib::collections;
using namespace cppflib::exceptions;

namespace cppflib
{

namespace net
{

/*************** VirtualHostInfo ****************/

VirtualHostInfo::VirtualHostInfo()
{
}

/**
 *  To setup the first (default) virtual host info
 *
 *
 *  VirtualHostInfo is structured like this:
 *
 *
 *  VirtualHostInfo ---+-- listenPort, bindAddr (shared among virtual hosts listening on the same port)
 *                     |
 *                     +-- hostAddr1
 *                     |
 *                     +-- hostAddr2
 *                     |
 *                     +-- hostAddr3
 *                     |
 *                     +-- ...
 *
 *
 *  @param [in] listenPort -- the port to listen
 *  @param [in] bindAddr -- a specific IP address or empty string to listen on all interfaces
 *  @param [in] hostAddr -- human readable domain name (default virtual host)
 */
VirtualHostInfo::VirtualHostInfo(u16_t listenPort, const CFString & bindAddr, const CFString & hostAddr)
{
   this->listenPort = listenPort;
   this->pBindAddr = new CFString(bindAddr);
   cppmempool::Retain(this->pBindAddr);
   this->pVirtualHostList = new ListCppObjT<BaseObject>();
   cppmempool::Retain(this->pVirtualHostList);

   if (!hostAddr.IsEmpty())
      pVirtualHostList->AddTail(new CFString(hostAddr));
}

VirtualHostInfo::~VirtualHostInfo()
{
   if (pBindAddr) {
      cppmempool::Release(pBindAddr);
      pBindAddr = NULL;
   }

   if (pVirtualHostList) {
      cppmempool::Release(pVirtualHostList);
      pVirtualHostList = NULL;
   }
}

/**
 *  Add one more virtual host other than the default which also binds to the same [address, port] pair
 *
 *  @return true -- adding OK
 *  @return false -- host name clashed with existing ones
 */
bool VirtualHostInfo::AddVirtualHost(const CFString & hostAddr)
{
   for (int i = 0 ; i < static_cast<int>(pVirtualHostList->GetCount()); ++i) {
      if (hostAddr.CompareNoCase(*static_cast<CFString*>(pVirtualHostList->Get(i))) == 0) // host name must be different
         return false;
   }

   pVirtualHostList->AddTail(new CFString(hostAddr));
   return true;
}

u16_t VirtualHostInfo::GetListenPort()
{
   return listenPort;
}

const CFString & VirtualHostInfo::GetBindAddr()
{
   return *pBindAddr;
}

const collections::ListCppObjT<BaseObject> & VirtualHostInfo::GetVHList()
{
   return *pVirtualHostList;
}


/*************** SocketFactory ****************/

SocketFactory::SocketFactory(void)
{
   this->isServer = false;
   this->pVirtualHostInfoList = NULL;
}

SocketFactory::SocketFactory(collections::ListCppObjT<VirtualHostInfo> * pVHInfoList)
{
   this->isServer = true;
   this->pVirtualHostInfoList = pVHInfoList;
   cppmempool::Retain(this->pVirtualHostInfoList);
   if (!CheckDuplicatedPort())
      THROW_CTOR_EXCEP(RuntimeException, _S("Port number duplicated"));
}

SocketFactory::~SocketFactory(void)
{
   ReleaseBeforeThrowing();
}

void SocketFactory::ReleaseBeforeThrowing()
{
   if (pVirtualHostInfoList) {
      cppmempool::Release(pVirtualHostInfoList);
      pVirtualHostInfoList = NULL;
   }
}


/**
 * Virtual host list should not contain same port number
 */
bool SocketFactory::CheckDuplicatedPort()
{
   for (int i = 0; i < static_cast<int>(pVirtualHostInfoList->GetCount()) - 1; ++i) {
      VirtualHostInfo *pVHInfo = pVirtualHostInfoList->Get(i);
      for (int j = i + 1; j < static_cast<int>(pVirtualHostInfoList->GetCount()); ++j) {
         if (pVHInfo->listenPort == pVirtualHostInfoList->Get(j)->listenPort)
            return false;
      }
   }

   return true;
}

/**
 *  Create a single client socket 
 *
 *  @param [in] isSSL -- this must be false. This param is only used by SSLSocketFactory
 *
 *  @return a normal TCP socket (can be either client or server)
 *  @return NULL (create/binding failed)
 */
Socket * SocketFactory::CreateSocket(bool isSSL, const CFString &socketAddress, u16_t socketPort)
{
   if (isSSL)
      return NULL;

   Socket *pSock = new Socket();
   if (!pSock->Create(socketAddress, socketPort, Socket::Stream)) {
      cppmempool::Release(pSock);
      pSock = NULL;
   }

   return pSock;
}

/**
 *  Create listening sockets for a server
 */
bool SocketFactory::CreateListeningSockets(ListCppObjT<Socket> &sockList, int backLog)
{
   if (!isServer || !pVirtualHostInfoList)
      return false;

   for (int i = 0; i < static_cast<int>(pVirtualHostInfoList->GetCount()); ++i) {
      VirtualHostInfo *pVHInfo = pVirtualHostInfoList->Get(i);

      Socket *pListenSock = CreateSocket(false, *pVHInfo->pBindAddr, pVHInfo->listenPort);
      if (!pListenSock) {
         sockList.Clear();
         break;
      }
         
      pListenSock->Listen(backLog);
      sockList.AddTail(pListenSock);
   }

   return (sockList.GetCount() > 0);
}

} // end of namespace net

} // end of namespace cppflib
